package com.dage.salesflow.service;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.util.ListUtils;
import com.dage.salesflow.constant.ImportMode;
import com.dage.salesflow.constant.SysConstant;
import com.dage.salesflow.constant.YesNoEnum;
import com.dage.salesflow.excel.*;
import com.dage.salesflow.kit.DbKit;
import com.dage.salesflow.kit.ObjectKit;
import com.dage.salesflow.kit.Ret;
import com.dage.salesflow.model.Area;
import com.dage.salesflow.model.User;
import com.dage.salesflow.model.base.BaseModel;
import com.dage.salesflow.model.vo.QuerySql;
import com.dage.salesflow.validation.DelForeignInfo;
import com.dage.salesflow.validation.ValidationInfo;
import com.dage.salesflow.validation.ValidationKit;
import com.jfinal.aop.Inject;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.*;
import io.jboot.exception.JbootException;
import io.jboot.service.JbootServiceBase;
import io.jboot.utils.CollectionUtil;
import org.apache.commons.collections4.CollectionUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

public abstract class BaseService<M extends BaseModel<M>, EXCEL> extends JbootServiceBase<M> implements ImportService<M, EXCEL> {

	@Inject
	UserService userService;
	@Inject
	AreaService areaService;

	Class recordClass;

	Class excelClass;

	private final ValidationInfo validationInfo;
	private final boolean logicDelete;
	private final boolean withDataScope;
	/**
	 * 导入模式，严格模式下任何警告或错误直接导入失败
	 */
	ImportMode importMode = ImportMode.STRICT;

	public BaseService() {
		//此处获取的是cglib动态代理的子类，其父类才是原始类
		Class<? extends BaseService> clazz = getClass();
		Class superclass = (Class) clazz.getGenericSuperclass();
		// 通过反射获取当前对象的class
		// getGenericSuperclass()获得带有泛型的父类
		ParameterizedType type = (ParameterizedType) superclass.getGenericSuperclass();
		// ParameterizedType参数化类型，即泛型, 多个泛型下标从0开始
		Type[] types = type.getActualTypeArguments();
		recordClass = (Class) types[0];
		excelClass = (Class) types[1];
		validationInfo = ValidationKit.getValidationInfo(excelClass);
		try {
			M record = (M) recordClass.newInstance();
			logicDelete = record.logicDelete;
			withDataScope = record.withDataScope;
		} catch (Exception e) {
			throw new JbootException(e);
		}

	}

	/**
	 * 分页
	 */
	public Page<Record> listByPage(M model, int pageNum, int pageSize) {
		QuerySql querySql = listSql(model, true);
		return Db.paginate(pageNum, pageSize, querySql.selectSql, querySql.sqlExceptSelect, querySql.paras.toArray());
	}

	/**
	 * 导出
	 */
	public File export(M model) throws Exception {
		QuerySql querySql = listSql(model, true);
		List<Record> records = Db.find(querySql.selectSql + " " + querySql.sqlExceptSelect, querySql.paras.toArray());
		return doExport(records, validationInfo.name + System.currentTimeMillis());
	}

	/**
	 * 分页查询和导出时的查询方法
	 *
	 * @param model 查询vo（额外的查询vo属性可以在Model中添加）
	 */
	abstract QuerySql listSql(M model, boolean order);

	@Override
	public Object save(M model) {
		model.setCreateUser(StpUtil.getLoginIdAsInt());
		model.setCreateTime(new Date());
		if (logicDelete) {
			model.setIsDeleted(YesNoEnum.NO.getValue());
		}
		Object ret = super.save(doSave(model));
		addCache(model);
		return ret;
	}

	/**
	 * 某些表没有公共字段的，可以覆写此方法，model.remove("is_deleted")
	 */
	protected M doSave(M model) {
		return model;
	}

	/**
	 * 某些表没有公共字段的，可以覆写此方法，model.remove("is_deleted")
	 */
	protected M doUpdate(M model) {
		return model;
	}

	@Override
	public boolean update(M model) {
		removeCache(model.getId());
		addCache(model);
		model.setUpdateTime(new Date());
		model.setUpdateUser(StpUtil.getLoginIdAsInt());
		return super.update(doUpdate(model));
	}

	@Override
	public boolean deleteById(Object id) {
		removeCache(id);
		if (!logicDelete) {
			return super.deleteById(id);
		}
		Table table = DAO._getTable();
		String sql = "UPDATE " + table.getName() + " SET is_deleted=1,update_time=?,update_user=? WHERE ";
		String[] pks = table.getPrimaryKey();
		if (pks.length > 1) {
			throw new ActiveRecordException("deleteById不支持复合主键");
		}
		if (pks.length == 0) {
			throw new ActiveRecordException(table.getName() + "表未设置主键");
		}
		sql += pks[0] + "=?";
		return Db.update(sql, new Date(), StpUtil.getLoginIdAsInt(), id) > 0;
	}

	@Override
	public boolean batchDeleteByIds(Object... ids) {
		if (ids.length == 0) {
			return false;
		}
		for (Object id : ids) {
			removeCache(id);
		}
		if (!logicDelete) {
			return super.batchDeleteByIds(ids);
		}
		List<Object> paras = ListUtils.newArrayListWithExpectedSize(ids.length + 3);
		Table table = DAO._getTable();
		StringBuilder sql = new StringBuilder("UPDATE ");
		sql.append(table.getName());
		sql.append(" SET is_deleted=?,update_time=?,update_user=? WHERE ");
		paras.add(YesNoEnum.YES.getValue());
		paras.add(new Date());
		paras.add(StpUtil.getLoginIdAsInt());
		String[] pks = table.getPrimaryKey();
		if (pks.length > 1) {
			throw new ActiveRecordException("batchDeleteByIds不支持复合主键");
		}
		if (pks.length == 0) {
			throw new ActiveRecordException(table.getName() + "表未设置主键");
		}
		sql.append(pks[0]);
		sql.append(" IN ");
		sql = DbKit.buildInSqlPara(paras, sql, ids);
		return Db.update(sql.toString(), paras.toArray()) > 0;
	}

	@Override
	public boolean delete(M model) {
		removeCache(model.getId());
		if (logicDelete) {
			model.setIsDeleted(YesNoEnum.YES.getValue());
			return update(model);
		} else {
			return super.delete(model);
		}
	}

	/**
	 * 添加
	 */
	public Ret add(M model) throws Exception {
		Ret ret = addCheck(model);
		if (!ret.isOk()) {
			return ret;
		}
		if (save(model) != null) {
			return Ret.ok();
		} else {
			return Ret.err("添加失败");
		}
	}

	/**
	 * 添加时校验
	 */
	public Ret addCheck(M model) throws Exception {
		return uniqueCheck(model);
	}

	/**
	 * 修改
	 */
	public Ret edit(M model) throws Exception {
		Ret ret = editCheck(model);
		if (!ret.isOk()) {
			return ret;
		}
		if (update(model)) {
			return Ret.ok();
		} else {
			return Ret.err("修改失败");
		}
	}

	/**
	 * 修改时校验
	 */
	public Ret editCheck(M model) throws Exception {
		return uniqueCheck(model);
	}

	/**
	 * 删除
	 */
	public Ret del(int id) {
		Ret ret = delCheck(id);
		if (!ret.isOk()) {
			return ret;
		}
		if (deleteById(id)) {
			return Ret.ok();
		} else {
			return Ret.err("删除失败");
		}
	}

	/**
	 * 批量删除
	 */
	public Ret delByIds(int... ids) {
		for (int id : ids) {
			Ret ret = delCheck(id);
			if (!ret.isOk()) {
				return ret;
			}
		}
		if (batchDeleteByIds(ids)) {
			return Ret.ok();
		} else {
			return Ret.err("删除失败");
		}
	}

	/**
	 * 删除时校验
	 */
	protected Ret delCheck(Object id) {
		Set<DelForeignInfo> delForeignInfo = ValidationKit.getDelForeignInfo(recordClass);
		if (delForeignInfo != null) {
			for (DelForeignInfo info : delForeignInfo) {
				long num = Db.queryLong(info.sql, id);
				if (num > 0) {
					return Ret.err("有" + num + "条关联的" + info.name + "数据，删除失败！");
				}
			}
		}
		return Ret.ok();
	}

	/**
	 * 模块中需要缓存数据时覆写此方法
	 */
	protected void addCache(M model) {
	}

	/**
	 * 模块中需要缓存数据时覆写此方法
	 */
	protected void removeCache(Object id) {
	}

	/**
	 * 导入数据
	 *
	 * @param file   Excel数据文件
	 * @param update 是否覆盖更新模式
	 * @return
	 */
	public Ret importData(File file, boolean update) {
		List<Ret> errors = ListUtils.newArrayList();
		try {
			EasyExcel.read(file, excelClass, new ExcelModelReader<EXCEL, M>(excelClass, recordClass, this, errors, update, importMode)).sheet().doRead();
		} catch (ExcelDataConvertException ex) {
			errors.add(new Ret(SysConstant.IMPORT_ERROR, "数据格式错误", ex.getRowIndex()));
		} finally {
			//完成导入后删除上传的文件
			file.delete();
		}
		if (CollectionUtils.isEmpty(errors)) {
			return Ret.ok();
		} else {
			return Ret.err(SysConstant.IMPORT_ERROR, errors);
		}
	}

	/**
	 * 由Excel模型对象设置数据库实体对象的属性，二者中相同的属性已自动完成设置
	 *
	 * @param importModel 导入数据对象
	 */
	@Override
	public Ret setModelData(ImportModel<EXCEL, M> importModel, AnalysisContext context) throws Exception {
		beforeSetExcelData(importModel);
		Ret ret;
		//非空验证
		for (Map.Entry<Field, ValidationInfo.NotNullInfo> entry : validationInfo.notNullMap.entrySet()) {
			Field field = entry.getKey();
			ValidationInfo.NotNullInfo notNull = entry.getValue();
			Object value = field.get(importModel.excel);
			if (value == null || StrKit.isBlank(String.valueOf(value))) {
				ret = new Ret(SysConstant.IMPORT_ERROR, notNull.msg, importModel.rowIndex);
				if (importMode != ImportMode.SOFT) {
					return ret;
				}
			}
		}
		StringBuilder warn = new StringBuilder();
		//外键关联
		for (ValidationInfo.ForeignKeyInfo keyInfo : validationInfo.foreignKeyMap.values()) {
			Object value = keyInfo.fieldInfo.excelField.get(importModel.excel);
			if (value == null || StrKit.isBlank(String.valueOf(value))) {
				if (keyInfo.notNull) {
					ret = new Ret(SysConstant.IMPORT_ERROR, keyInfo.msg, importModel.rowIndex);
					if (importMode != ImportMode.SOFT) {
						return ret;
					}
				} else {
					continue;
				}
			}
			ret = setForeignKey(keyInfo, importModel, value);
			if (ret.getCode() == SysConstant.IMPORT_ERROR) {
				if (importMode != ImportMode.SOFT) {
					return ret;
				} else {
					if (warn.length() > 0) {
						warn.append("<br/>");
					}
					warn.append(keyInfo.msg);
					continue;
				}
			} else if (ret.getCode() == SysConstant.IMPORT_WARN) {
				if (warn.length() > 0) {
					warn.append("<br/>");
				}
				warn.append(keyInfo.msg);
			}
		}
		//字典字段
		for (ValidationInfo.DictKeyInfo keyInfo : validationInfo.dictKeyMap.values()) {
			Object value = keyInfo.fieldInfo.excelField.get(importModel.excel);
			if (value == null || StrKit.isBlank(String.valueOf(value))) {
				if (keyInfo.notNull) {
					ret = new Ret(SysConstant.IMPORT_ERROR, keyInfo.msg, importModel.rowIndex);
					if (importMode != ImportMode.SOFT) {
						return ret;
					}
				} else {
					continue;
				}
			}
			String sql = "SELECT id FROM " + SysConstant.DICT_TABLE + " WHERE " + SysConstant.DICT_KEY + "=?" + " AND " + SysConstant.DICT_VALUE + "=?";
			Integer id = Db.queryFirst(sql, keyInfo.dictKey, value);
			if (id != null) {
				keyInfo.fieldInfo.recordSetter.invoke(importModel.record, id);
			} else {
				if (keyInfo.notNull) {
					ret = new Ret(SysConstant.IMPORT_ERROR, keyInfo.msg, importModel.rowIndex);
					if (importMode != ImportMode.SOFT) {
						return ret;
					}
				} else {
					if (warn.length() > 0) {
						warn.append("<br/>");
					}
					warn.append(keyInfo.msg);
				}
			}
		}

		//正则验证
		for (Map.Entry<Field, ValidationInfo.RegexValidationInfo> entry : validationInfo.regExMap.entrySet()) {
			Field field = entry.getKey();
			ValidationInfo.RegexValidationInfo regex = entry.getValue();
			Object value = field.get(importModel.excel);
			String strValue = String.valueOf(value);
			if (value == null || StrKit.isBlank(strValue)) {
				continue;
			}
			if (!strValue.matches(regex.pattern)) {
				regex.recordSetter.invoke(importModel.record, new Object[]{null});
				if (warn.length() > 0) {
					warn.append("<br/>");
				}
				warn.append(regex.msg);
			}
		}
		//枚举属性
		for (ValidationInfo.EnumPropInfo enumPropInfo : validationInfo.enumPropMap.values()) {
			Object value = enumPropInfo.fieldInfo.excelField.get(importModel.excel);
			String strValue = String.valueOf(value);
			if (value == null || StrKit.isBlank(strValue)) {
				continue;
			}
			ret = setEnumProp(enumPropInfo, importModel, strValue);
			if (ret.getCode() == SysConstant.IMPORT_ERROR && importMode != ImportMode.SOFT) {
				return ret;
			} else if (ret.getCode() == SysConstant.IMPORT_WARN) {
				if (warn.length() > 0) {
					warn.append("<br/>");
				}
				warn.append(enumPropInfo.msg);
			}
		}
		ret = afterSetExcelData(importModel);
		if (ret.getCode() == SysConstant.IMPORT_ERROR && importMode != ImportMode.SOFT) {
			return ret;
		} else if (warn.length() > 0 || ret.getCode() == SysConstant.IMPORT_WARN) {
			if (StrKit.notBlank(ret.getMsg())) {
				if (warn.length() > 0) {
					warn.append("<br/>");
				}
				warn.append(ret.getMsg());
			}
			return new Ret(SysConstant.IMPORT_WARN, warn.toString(), importModel.rowIndex);
		}
		return ret;
	}

	/**
	 * 设置枚举属性
	 *
	 * @param enumPropInfo 枚举属性信息
	 * @param importModel  导入数据对象
	 * @param excelValue   导入的excel数据
	 */
	protected Ret setEnumProp(ValidationInfo.EnumPropInfo enumPropInfo, ImportModel<EXCEL, M> importModel, String excelValue) throws Exception {
		boolean matched = false;
		for (int i = 0; i < enumPropInfo.props.length; i++) {
			if (enumPropInfo.props[i].equals(excelValue)) {
				enumPropInfo.fieldInfo.recordSetter.invoke(importModel.record, i);
				matched = true;
			}
		}
		if (!matched) {
			return new Ret(SysConstant.IMPORT_WARN, enumPropInfo.msg, importModel.rowIndex);
		}
		return Ret.ok();
	}

	/**
	 * 设置外键信息（非空验证已完成）
	 *
	 * @param keyInfo     外键约束信息
	 * @param importModel 导入数据对象
	 * @param excelValue  导入的excel数据
	 */
	protected Ret setForeignKey(ValidationInfo.ForeignKeyInfo keyInfo, ImportModel<EXCEL, M> importModel, Object excelValue) throws Exception {
		String sql = "SELECT id FROM " + keyInfo.table + " tt WHERE 1=1";
		List<Object> paras = ListUtils.newArrayList();
		if (keyInfo.logicDelete) {
			sql += " AND is_deleted=?";
			paras.add(YesNoEnum.NO.getValue());
		}
		sql += " AND " + keyInfo.fieldInfo.column + "=?";
		paras.add(excelValue);
		if (StrKit.notBlank(keyInfo.sql)) {
			sql += keyInfo.sql;
		}
		if (keyInfo.withDataScope) {
			sql += dataScope(paras, "tt");
		}
		Integer id = Db.queryFirst(sql, paras.toArray());
		if (id != null) {
			keyInfo.fieldInfo.recordSetter.invoke(importModel.record, id);
		} else {
			if (keyInfo.notNull) {
				return new Ret(SysConstant.IMPORT_ERROR, keyInfo.msg, importModel.rowIndex);
			} else {
				return new Ret(SysConstant.IMPORT_WARN, keyInfo.msg, importModel.rowIndex);
			}
		}
		return Ret.ok();
	}

	/**
	 * 由Excel模型对象设置数据库实体对象的属性，二者中相同的属性已自动完成设置，所有自定义验证已完成
	 *
	 * @param importModel 导入数据对象
	 */
	protected Ret afterSetExcelData(ImportModel<EXCEL, M> importModel) {
		return Ret.ok();
	}

	/**
	 * 由Excel模型对象设置数据库实体对象的属性，二者中相同的属性已自动完成设置，未进行自定义验证
	 *
	 * @param importModel 导入数据对象
	 */
	protected void beforeSetExcelData(ImportModel<EXCEL, M> importModel) {

	}

	/**
	 * 唯一性验证
	 *
	 * @param record
	 * @return
	 */
	protected Ret uniqueCheck(M record) throws Exception {
		String sql = "SELECT COUNT(*) FROM ";
		sql += validationInfo.table + " tt WHERE 1=1";
		List<Object> paras = ListUtils.newArrayList();
		for (ValidationInfo.UniqueKeyInfo info : validationInfo.uniqueKeyMap.values()) {
			paras.clear();
			paras.add(info.fieldInfo.recordGetter.invoke(record));
			String countSql = sql + " AND " + info.fieldInfo.column + "=?";
			if (record.getId() != null && record.getId() > 0) {
				//更新
				countSql += " AND id!=?";
				paras.add(record.getId());
			}
			if (info.withDataScope) {
				countSql += dataScope(paras, "tt");
			}
			if (info.logicDelete && logicDelete) {
				countSql += " AND is_deleted=?";
				paras.add(YesNoEnum.NO.getValue());
			}
			Long num = Db.queryLong(countSql, paras.toArray());
			if (num > 0) {
				return Ret.err(info.msg);
			}
		}
		paras.clear();
		if (CollectionUtil.isEmpty(validationInfo.importKey.fieldInfos)) {
			return Ret.ok();
		}
		String uniqueSql = sql;
		for (ValidationInfo.FieldInfo info : validationInfo.importKey.fieldInfos) {
			uniqueSql += " AND " + info.column + "=?";
			paras.add(info.recordGetter.invoke(record));
		}
		if (record.getId() != null && record.getId() > 0) {
			//更新
			uniqueSql += " AND id!=?";
			paras.add(record.getId());
		}
		if (validationInfo.importKey.withDataScope) {
			uniqueSql += dataScope(paras, "tt");
		}
		if (validationInfo.importKey.logicDelete && logicDelete) {
			uniqueSql += " AND is_deleted=?";
			paras.add(YesNoEnum.NO.getValue());
		}
		Long num = Db.queryLong(uniqueSql, paras.toArray());
		if (num > 0) {
			return Ret.err(validationInfo.importKey.msg);
		}
		return Ret.ok();
	}

	@Override
	public Integer queryId(ImportModel<EXCEL, M> importModel) throws Exception {
		if (CollectionUtil.isEmpty(validationInfo.importKey.fieldInfos)) {
			throw new ExcelAnalysisException("导入唯一性识别字段未设置");
		}
		String sql = "SELECT id FROM ";
		sql += validationInfo.table + " tt WHERE 1=1";
		List<Object> paras = ListUtils.newArrayList();
		for (ValidationInfo.FieldInfo info : validationInfo.importKey.fieldInfos) {
			sql += " AND " + info.column + "=?";
			paras.add(info.recordGetter.invoke(importModel.record));
		}
		if (validationInfo.importKey.withDataScope) {
			sql += dataScope(paras, "tt");
		}
		if (validationInfo.importKey.logicDelete && logicDelete) {
			sql += " AND is_deleted=?";
			paras.add(YesNoEnum.NO.getValue());
		}
		return Db.queryInt(sql, paras.toArray());
	}

	@Override
	public void setModelId(M m, Integer id) {
		m.setId(id);
	}

	protected File doExport(List<Record> records, String fileName) throws Exception {
		List<EXCEL> datas = ObjectKit.records2Beans(records, excelClass);
		//处理枚举类型属性导出
		for (EXCEL excel : datas) {
			for (Map.Entry<Field, ValidationInfo.EnumPropInfo> entry : validationInfo.enumPropMap.entrySet()) {
				Field field = entry.getKey();
				ValidationInfo.EnumPropInfo propInfo = entry.getValue();
				Object value = field.get(excel);
				if (value != null && value.getClass() == Integer.class) {
					Integer idx = (Integer) value;
					propInfo.fieldInfo.excelField.set(excel, propInfo.props[idx]);
				}
			}
		}

		Path tempFile = Files.createTempFile(fileName, ".xlsx");
		File file = tempFile.toFile();
		EasyExcel.write(file, excelClass)
				.registerWriteHandler(new ExcelWriteHandler(excelClass, false))
				.registerWriteHandler(new AutoColumnWidthStyleStrategy())
				.sheet()
				.doWrite(datas);
		return file;
	}

	/**
	 * 设置数据权限sql
	 *
	 * @param paras 参数
	 * @param alias 主表别名
	 * @return
	 */
	public String dataScope(List<Object> paras, String alias) {
		User user = userService.getById(StpUtil.getLoginIdAsInt());
		Area area = areaService.getById(user.getAreaId());
		paras.add(area.getCode() + "%");
		return " AND " + alias + ".area_id IN(SELECT id FROM t_area WHERE `code` LIKE ?)";
	}

	/**
	 * 一键删除
	 *
	 * @param model 用于查询条件
	 * @param alias 主表查询别名
	 * @param check 是否删除校验
	 */
	public Ret listDelete(M model, String alias, boolean check) {
		QuerySql querySql = listSql(model, false);
		if (check) {
			List<Object> ids = Db.query("SELECT id FROM(SELECT " + alias + ".id " + querySql.sqlExceptSelect + ") t", querySql.paras.toArray());
			for (Object id : ids) {
				Ret ret = delCheck(id);
				if (!ret.isOk()) {
					return ret;
				}
			}
		}
		if (logicDelete) {
			Db.update("UPDATE " + DAO._getTableName() + " SET is_deleted=1 WHERE id IN(SELECT id FROM(SELECT " + alias + ".id " + querySql.sqlExceptSelect + ") t)", querySql.paras.toArray());
		} else {
			Db.update("DELETE FROM " + DAO._getTableName() + " WHERE id IN(SELECT id FROM(SELECT " + alias + ".id " + querySql.sqlExceptSelect + ") t)", querySql.paras.toArray());
		}
		return Ret.ok();
	}

	/**
	 * 批量删除
	 *
	 * @param ids
	 * @param check 是否删除校验
	 */
	public Ret batchDelete(String[] ids, boolean check) {
		if (check) {
			for (String id : ids) {
				Ret ret = delCheck(id);
				if (!ret.isOk()) {
					return ret;
				}
			}
		}
		return batchDeleteByIds(ids) ? Ret.ok() : Ret.err("删除失败");
	}
}
